package com.liziguo.lzgfp.system.util;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @Author: Liziguo
 * @Date: 2020/6/16 17:21
 */


public class RSAUtil {

    //定义加密方式
//	public static final String KEY_RSA = "RSA";

//    public static final String TRANSFORMATION = "RSA";
    //加密方式，android的
//	public static final String TRANSFORMATION = "RSA/None/NoPadding";
    //加密方式，标准jdk的
//	public static final String TRANSFORMATION = "RSA/None/PKCS1Padding";

    //加密方式，android的
//	public static final String TRANSFORMATION = "RSA/ECB/NoPadding";
    //加密方式，标准jdk的
    public static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";
//    使用SHA-256和RSA 2048进行加密和签名
//    public static final String TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";

    //定义公钥关键词
//	public static final String KEY_RSA_PUBLICKEY = "RSAPublicKey";
    //定义私钥关键词
//	public static final String KEY_RSA_PRIVATEKEY = "RSAPrivateKey";
    //定义签名算法
//	private final static String KEY_RSA_SIGNATURE = "MD5withRSA";

    //1024位的证书，加密时最大支持117个字节，解密时为128；
    //2048位的证书，加密时最大支持245个字节，解密时为256。
    //加密时支持的最大字节数：证书位数/8 -11（比如：2048位的证书，支持的最大加密字节数：2048/8 - 11 = 245）
    public static final int KEY_LENGTH = 2048;
    //使用RSA/ECB/PKCS1Padding的话-11 使用RSA/ECB/OAEPWithSHA-256AndMGF1Padding的话-66
    public static final int MAX_ENCR_LEN = KEY_LENGTH / 8 - 11;
    public static final int MAX_DECR_LEN = KEY_LENGTH / 8;


    /**
     * 生成公匙和私匙
     *
     * @return RSAKey
     */

    public static RSAKey genKey() throws NoSuchAlgorithmException {
        final KeyPairGenerator keyp = KeyPairGenerator.getInstance("RSA");
        keyp.initialize(KEY_LENGTH, new SecureRandom());
        final KeyPair key = keyp.generateKeyPair();
        final String publickey = Base64.getEncoder().encodeToString(key.getPublic().getEncoded());
        final String privatekey = Base64.getEncoder().encodeToString(key.getPrivate().getEncoded());
        return new RSAKey(publickey, privatekey);
    }

    /**
     * 加密
     *
     * @param data      明文
     * @param publicKey 公匙
     * @return 密文
     * @throws Exception
     */

    public static String encrypt(String data, String publicKey) throws Exception {
        byte[] key = Base64.getDecoder().decode(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(key));
        // RSA加密
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);

        final byte[] dataBytes = data.getBytes("UTF-8");
        byte[] bytes = new byte[0];
        for (int i = 0; i < dataBytes.length; i += MAX_ENCR_LEN) {
//			final byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(dataBytes, i, i + MAX_ENCR_LEN));
            final int len = Math.min(dataBytes.length - i, MAX_ENCR_LEN);
            final byte[] doFinal = cipher.doFinal(dataBytes, i, len);
            final byte[] temp = new byte[bytes.length + doFinal.length];
            System.arraycopy(bytes, 0, temp, 0, bytes.length);
            System.arraycopy(doFinal, 0, temp, bytes.length, doFinal.length);
            bytes = temp;
        }
//		String out = java.util.Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes("utf-8")));// 加上UTF-8加密中文会出现乱码
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 解密
     *
     * @param data       密文
     * @param privateKey 私匙
     * @return 明文
     * @throws Exception
     */

    public static String decrypt(String data, String privateKey) throws Exception {
        // base64编码的私钥
        byte[] key = Base64.getDecoder().decode(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(key));
        // RSA解密
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, priKey);

        byte[] bytes = new byte[0];
        final byte[] inputByte = Base64.getDecoder().decode(data);//加上UTF-8加密中文会出现乱码
        for (int i = 0; i < inputByte.length; i += MAX_DECR_LEN) {
//			byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(inputByte, i, i + MAX_DECR_LEN));
            final int len = Math.min(inputByte.length - i, MAX_DECR_LEN);
            final byte[] doFinal = cipher.doFinal(inputByte, i, len);
            final byte[] temp = new byte[bytes.length + doFinal.length];
            System.arraycopy(bytes, 0, temp, 0, bytes.length);
            System.arraycopy(doFinal, 0, temp, bytes.length, doFinal.length);
            bytes = temp;
        }
        return new String(bytes, "UTF-8");
//		return  new String(cipher.doFinal(inputByte), "utf-8");
    }

    /**
     * 签名
     *
     * @param data       明文
     * @param privateKey 私匙
     * @return 签名
     * @throws Exception
     */

    public static String sign(String data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey key = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(key);
        signature.update(data.getBytes());
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 验签
     *
     * @param srcData   明文
     * @param publicKey 公匙
     * @param sign      签名
     * @return 是否验证通过
     * @throws Exception
     */

    public static boolean verify(String srcData, String publicKey, String sign) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey key = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(key);
        signature.update(srcData.getBytes());
        return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
    }

    public static void main(String[] args) throws Exception {
        RSAKey rsaKey = genKey();
        String s = "asd";
//        String s = "mxVRS5PePwyCmg3nua1Yq8S7Maib7rRvgWM3BphlArGA+fCl/XLXv6aVTi+QxXNv7xocVM4H1FjDw/AawAR8lDGBiEt/pNi8/ABbtTaIpzNgZFeYUiVkhoeW9YvSC3rYH8ODcdH/MKXGwfC2Yy88v2easnHJRbc8xz9NwJKj7u+kACbnTm7ckTY6JMOhat5y/J3fuCoLavqJ6PeOftu6yRnMy+nb7VZDGJ2YFcsO1i/HqFqa7OfRQ2ZOaU24WxjoN7HClc0iEjJfW3CN/qHX4iaGwbsnGpoUe+6vuR1jjbY1NnYROszFB2lz61LU4/zbELIZxqCU7irc4XOcfxKBO6D7lRcPa7ZCDbW7WIYkSIcG/S5WI7OU9H7H4KlSO1lQiiUjRQ1Cccd6WyGvZ/rPE3rYKgn/3YwtJxxS9xqkvtOEXqXgktP1zAVkljDn99M3ey1b+PmbP/Gq/QG9bClE4allqRY/v3akSvugyG7dSyfVhZ+PBqSTW8ST0cgVWQ4YdQ1Tqr2XistZ8UAizqFhYg0kJca5BVPOtZCgsreszhMpQ6QD5IECK5EtO4WyiwNEA1uzKJr4fFcKYQ13CYEtcncob0TmP+AxuQ+4wgsH+ZMu6GGxGBNQuSGXxmk5Ne7Zb35deoYaJyE3+szeG0jdIuoTfyMtvIw1YJK3EM8DE/YfJPP6wJzMoZVbqNZwMibYTUphuDc7BB10URX4W+GFc2w4cKb7/Ls4Yf3oeMdn6pvXX1+sYdhXjUOJWZQyx0hIfrHiA7fYQ8kr8uFxrpRMCNYQwoZaNx8AG7IQ/U6fCPkCVardxidR6jaCmDnmKUbuNrw7kvq0GUAP4Yxtpv+OoX6fMWfW4bheZeVNQ6MPLi/DjH2CS4gEoJNDOr+oP7Tc654cA9cFZP2KUimZLxEt3Z1DuJFGUYWkrQrlE181wCMNjJZvXl0mmgtqumPU36o97DhLH7DHjrDUmcRV2y41j95J9P+tOJSEd1tWIvXM8Gu05AVSAluVBla+OO63WulL21520EzQYPSN6+hAQeLTpJ46EKZA/qnU51xEtDqFNoClYSCrOplp+1J3GMVdikIQ9aQ49rmmYkxDlO395CCA8+5RLYyZMBc7YA+grMWTOoA38fhQawlki8Ap4cXA1s4o3kCh9R3x4PQigjgUCkyoTSlUFU/ovh7UYsd/+CtDxmWIMpiTIaG/Agv066HBpiWmFbUPd07AXN+FXCa8T3NeDfBfBa7emnB3kaKLlPIg2c5QMmigK4vH5rcVVnOGo2Av+DU8l1IZ2kSS8RcVCNuBeCSbY+Hr2tZ8nlDzcPves3irVZhgCdjz4ENdi536Cusg1Y8lzyJ3Fj6CJw0qbt0gZw==\n哈哈";
        String enc = encrypt(s, rsaKey.publickey());
        System.out.println("enc = " + enc);
        String dec = decrypt(enc, rsaKey.privatekey());
        System.out.println("dec = " + dec);
        String sign = sign(dec, rsaKey.privatekey());
        System.out.println("sign = " + sign);
        System.out.println(verify(s, rsaKey.publickey(), sign));
    }

    public record RSAKey(String publickey, String privatekey) {
    }

}

